package com.example.component;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.graphics.drawable.shapes.Shape;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Scroller;

public class HorizontalPagerWithPageControl extends ViewGroup {
	/*
	 * How long to animate between screens when programmatically setting with
	 * setCurrentScreen using the animate parameter
	 */
	private static final int ANIMATION_SCREEN_SET_DURATION_MILLIS = 500;
	// What fraction (1/x) of the screen the user must swipe to indicate a page
	// change
	private static final int FRACTION_OF_SCREEN_WIDTH_FOR_SWIPE = 4;
	private static final int INVALID_SCREEN = -1;
	/*
	 * Velocity of a swipe (in density-independent pixels per second) to force a
	 * swipe to the next/previous screen. Adjusted into
	 * mDensityAdjustedSnapVelocity on init.
	 */
	private static final int SNAP_VELOCITY_DIP_PER_SECOND = 600;
	// Argument to getVelocity for units to give pixels per second (1 = pixels
	// per millisecond).
	private static final int VELOCITY_UNIT_PIXELS_PER_SECOND = 1000;
	private static final int TOUCH_STATE_REST = 0;
	private static final int TOUCH_STATE_HORIZONTAL_SCROLLING = 1;
	private static final int TOUCH_STATE_VERTICAL_SCROLLING = -1;
	private int m_currentScreen;
	private int m_densityAdjustedSnapVelocity;
	private boolean m_firstLayout = true;
	private float m_lastMotionX;
	private float m_lastMotionY;
	private OnScreenSwitchListener m_onScreenSwitchListener;
	private int m_maximumVelocity;
	private int m_nextScreen = INVALID_SCREEN;
	private Scroller m_scroller;
	private int m_touchSlop;
	private int m_touchState = TOUCH_STATE_REST;
	private VelocityTracker m_velocityTracker;
	private int m_lastSeenLayoutWidth = -1;
	// Active and inactive draw
	private Drawable m_activeDrawable;
	private Drawable m_inactiveDrawable;
	// The size for the drawables
	private float m_indicatorSize;
	// The screen scale to get px to dip
	private static float SCALE;
	/**
	 * Simple constructor to use when creating a view from code.
	 * 
	 * @param p_context
	 *            The Context the view is running in, through which it can
	 *            access the current theme, resources, etc.
	 */
	public HorizontalPagerWithPageControl(final Context p_context) {
		super(p_context);
		init();
	}
	/**
	 * Constructor that is called when inflating a view from XML. This is called
	 * when a view is being constructed from an XML file, supplying attributes
	 * that were specified in the XML file. This version uses a default style of
	 * 0, so the only attribute values applied are those in the Context's Theme
	 * and the given AttributeSet.
	 * 
	 * <p>
	 * The method onFinishInflate() will be called after all children have been
	 * added.
	 * 
	 * @param p_context
	 *            The Context the view is running in, through which it can
	 *            access the current theme, resources, etc.
	 * @param p_attrs
	 *            The attributes of the XML tag that is inflating the view.
	 * @see #View(Context, AttributeSet, int)
	 */
	public HorizontalPagerWithPageControl(final Context p_context,
			final AttributeSet p_attrs) {
		super(p_context, p_attrs);
		init();
	}
	/**
	 * Sets up the scroller and touch/fling sensitivity parameters for the
	 * pager.
	 */
	private void init() {
		m_scroller = new Scroller(getContext());
		// get the screen density
		SCALE = getResources().getDisplayMetrics().density;
		// set the indicator size resolution independent
		m_indicatorSize = (7 * SCALE);
		// Calculate the density-dependent snap velocity in pixels
		DisplayMetrics m_displayMetrics = new DisplayMetrics();
		((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE))
		.getDefaultDisplay().getMetrics(m_displayMetrics);
		m_densityAdjustedSnapVelocity = (int) (m_displayMetrics.density * SNAP_VELOCITY_DIP_PER_SECOND);
		final ViewConfiguration m_configuration = ViewConfiguration
				.get(getContext());
		m_touchSlop = m_configuration.getScaledTouchSlop();
		m_maximumVelocity = m_configuration.getScaledMaximumFlingVelocity();
		// draw the shapes
		makeShapes();
	}
	@Override
	protected void onMeasure(final int p_widthMeasureSpec,
			final int p_heightMeasureSpec) {
		super.onMeasure(p_widthMeasureSpec, p_heightMeasureSpec);
		final int m_width = MeasureSpec.getSize(p_widthMeasureSpec);
		final int m_widthMode = MeasureSpec.getMode(p_widthMeasureSpec);
		if (m_widthMode != MeasureSpec.EXACTLY) {
			throw new IllegalStateException(
					"ViewSwitcher can only be used in EXACTLY mode.");
		}
		final int m_heightMode = MeasureSpec.getMode(p_heightMeasureSpec);
		if (m_heightMode != MeasureSpec.EXACTLY) {
			throw new IllegalStateException(
					"ViewSwitcher can only be used in EXACTLY mode.");
		}
		// The children are given the same width and height as the workspace
		final int m_count = getChildCount();
		for (int m_i = 0; m_i < m_count; m_i++) {
			getChildAt(m_i).measure(p_widthMeasureSpec, p_heightMeasureSpec);
		}
		if (m_firstLayout) {
			scrollTo(m_currentScreen * m_width, 0);
			m_firstLayout = false;
		}
		else if (m_width != m_lastSeenLayoutWidth) { // Width has changed
			/*
			 * Recalculate the width and scroll to the right position to be sure
			 * we're in the right place in the event that we had a rotation that
			 * didn't result in an activity restart (code by aveyD). Without
			 * this you can end up between two pages after a rotation.
			 */
			Display m_display = ((WindowManager) getContext().getSystemService(
					Context.WINDOW_SERVICE)).getDefaultDisplay();
			int m_displayWidth = m_display.getWidth();
			m_nextScreen = Math.max(0,
					Math.min(getCurrentScreen(), getChildCount() - 1));
			final int m_newX = m_nextScreen * m_displayWidth;
			final int m_delta = m_newX - getScrollX();
			m_scroller.startScroll(getScrollX(), 0, m_delta, 0, 0);
		}
		m_lastSeenLayoutWidth = m_width;
	}
	@Override
	protected void onLayout(final boolean p_changed, final int p_l,
			final int p_t, final int p_r, final int p_b) {
		int m_childLeft = 0;
		final int m_count = getChildCount();
		for (int m_i = 0; m_i < m_count; m_i++) {
			final View m_child = getChildAt(m_i);
			if (m_child.getVisibility() != View.GONE) {
				final int m_childWidth = m_child.getMeasuredWidth();
				m_child.layout(m_childLeft, 0, m_childLeft + m_childWidth,
						m_child.getMeasuredHeight());
				m_childLeft += m_childWidth;
			}
		}
	}
	@Override
	public boolean onInterceptTouchEvent(final MotionEvent p_ev) {
		/*
		 * By Yoni Samlan: Modified onInterceptTouchEvent based on standard
		 * ScrollView's onIntercept. The logic is designed to support a nested
		 * vertically scrolling view inside this one; once a scroll registers
		 * for X-wise scrolling, handle it in this view and don't let the
		 * children, but once a scroll registers for y-wise scrolling, let the
		 * children handle it exclusively.
		 */
		final int m_action = p_ev.getAction();
		boolean m_intercept = false;
		switch (m_action) {
		case MotionEvent.ACTION_MOVE:
			/*
			 * If we're in a horizontal scroll event, take it (intercept further
			 * events). But if we're mid-vertical-scroll, don't even try; let
			 * the children deal with it. If we haven't found a scroll event
			 * yet, check for one.
			 */
			if (m_touchState == TOUCH_STATE_HORIZONTAL_SCROLLING) {
				/*
				 * We've already started a horizontal scroll; set intercept to
				 * true so we can take the remainder of all touch events in
				 * onTouchEvent.
				 */
				m_intercept = true;
			} else if (m_touchState == TOUCH_STATE_VERTICAL_SCROLLING) {
				// Let children handle the events for the duration of the scroll
				// event.
				m_intercept = false;
			} else { // We haven't picked up a scroll event yet; check for one.
				/*
				 * If we detected a horizontal scroll event, start stealing
				 * touch events (mark as scrolling). Otherwise, see if we had a
				 * vertical scroll event -- if so, let the children handle it
				 * and don't look to intercept again until the motion is done.
				 */
				final float m_x = p_ev.getX();
				final int m_xDiff = (int) Math.abs(m_x - m_lastMotionX);
				boolean m_xMoved = m_xDiff > m_touchSlop;
				if (m_xMoved) {
					// Scroll if the user moved far enough along the X axis
					m_touchState = TOUCH_STATE_HORIZONTAL_SCROLLING;
					m_lastMotionX = m_x;
				}
				final float m_y = p_ev.getY();
				final int m_yDiff = (int) Math.abs(m_y - m_lastMotionY);
				boolean m_yMoved = m_yDiff > m_touchSlop;
				if (m_yMoved) {
					m_touchState = TOUCH_STATE_VERTICAL_SCROLLING;
				}
			}
			break;
		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_UP:
			// Release the drag.
			m_touchState = TOUCH_STATE_REST;
			break;
		case MotionEvent.ACTION_DOWN:
			/*
			 * No motion yet, but register the coordinates so we can check for
			 * intercept at the next MOVE event.
			 */
			m_lastMotionY = p_ev.getY();
			m_lastMotionX = p_ev.getX();
			break;
		default:
			break;
		}
		return m_intercept;
	}
	@Override
	public boolean onTouchEvent(final MotionEvent p_ev) {
		if (m_velocityTracker == null) {
			m_velocityTracker = VelocityTracker.obtain();
		}
		m_velocityTracker.addMovement(p_ev);
		final int m_action = p_ev.getAction();
		final float m_x = p_ev.getX();
		switch (m_action) {
		case MotionEvent.ACTION_DOWN:
			/*
			 * If being flinged and user touches, stop the fling. isFinished
			 * will be false if being flinged.
			 */
			if (!m_scroller.isFinished()) {
				m_scroller.abortAnimation();
			}
			// Remember where the motion event started
			m_lastMotionX = m_x;
			if (m_scroller.isFinished()) {
				m_touchState = TOUCH_STATE_REST;
			} else {
				m_touchState = TOUCH_STATE_HORIZONTAL_SCROLLING;
			}
			break;
		case MotionEvent.ACTION_MOVE:
			final int m_xDiff = (int) Math.abs(m_x - m_lastMotionX);
			boolean m_xMoved = m_xDiff > m_touchSlop;
			if (m_xMoved) {
				// Scroll if the user moved far enough along the X axis
				m_touchState = TOUCH_STATE_HORIZONTAL_SCROLLING;
			}
			if (m_touchState == TOUCH_STATE_HORIZONTAL_SCROLLING) {
				// Scroll to follow the motion event
				final int m_deltaX = (int) (m_lastMotionX - m_x);
				m_lastMotionX = m_x;
				final int m_scrollX = getScrollX();
				if (m_deltaX < 0) {
					if (m_scrollX > 0) {
						scrollBy(Math.max(-m_scrollX, m_deltaX), 0);
					}
				} else if (m_deltaX > 0) {
					final int m_availableToScroll = getChildAt(
							getChildCount() - 1).getRight()
							- m_scrollX - getWidth();
					if (m_availableToScroll > 0) {
						scrollBy(Math.min(m_availableToScroll, m_deltaX), 0);
					}
				}
			}
			break;
		case MotionEvent.ACTION_UP:
			if (m_touchState == TOUCH_STATE_HORIZONTAL_SCROLLING) {
				final VelocityTracker m_velocityTrack = m_velocityTracker;
				m_velocityTrack.computeCurrentVelocity(
						VELOCITY_UNIT_PIXELS_PER_SECOND, m_maximumVelocity);
				int m_velocityX = (int) m_velocityTrack.getXVelocity();
				if (m_velocityX > m_densityAdjustedSnapVelocity
						&& m_currentScreen > 0) {
					// Fling hard enough to move left
					snapToScreen(m_currentScreen - 1);
				} else if (m_velocityX < -m_densityAdjustedSnapVelocity
						&& m_currentScreen < getChildCount() - 1) {
					// Fling hard enough to move right
					snapToScreen(m_currentScreen + 1);
				} else {
					snapToDestination();
				}
				if (m_velocityTracker != null) {
					m_velocityTracker.recycle();
					m_velocityTracker = null;
				}
			}
			m_touchState = TOUCH_STATE_REST;
			break;
		case MotionEvent.ACTION_CANCEL:
			m_touchState = TOUCH_STATE_REST;
			break;
		default:
			break;
		}
		return true;
	}
	@Override
	public void computeScroll() {
		if (m_scroller.computeScrollOffset()) {
			scrollTo(m_scroller.getCurrX(), m_scroller.getCurrY());
			postInvalidate();
		} else if (m_nextScreen != INVALID_SCREEN) {
			m_currentScreen = Math.max(0,
					Math.min(m_nextScreen, getChildCount() - 1));
			// Notify observer about screen change
			if (m_onScreenSwitchListener != null) {
				m_onScreenSwitchListener.onScreenSwitched(m_currentScreen);
			}
			m_nextScreen = INVALID_SCREEN;
		}
	}
	/**
	 * Returns the index of the currently displayed screen.
	 * 
	 * @return The index of the currently displayed screen.
	 */
	public int getCurrentScreen() {
		return m_currentScreen;
	}
	/**
	 * Sets the current screen.
	 * 
	 * @param p_currentScreen
	 *            The new screen.
	 * @param p_animate
	 *            True to smoothly scroll to the screen, false to snap instantly
	 */
	public void setCurrentScreen(final int p_currentScreen,
			final boolean p_animate) {
		m_currentScreen = Math.max(0,
				Math.min(p_currentScreen, getChildCount() - 1));
		if (p_animate) {
			snapToScreen(p_currentScreen, ANIMATION_SCREEN_SET_DURATION_MILLIS);
		} else {
			scrollTo(m_currentScreen * getWidth(), 0);
		}
		invalidate();
	}
	/**
	 * Sets the {@link OnScreenSwitchListener}.
	 * 
	 * @param onScreenSwitchListener
	 *            The listener for switch events.
	 */
	public void setOnScreenSwitchListener(
			final OnScreenSwitchListener onScreenSwitchListener) {
		m_onScreenSwitchListener = onScreenSwitchListener;
	}
	/**
	 * Snaps to the screen we think the user wants (the current screen for very
	 * small movements; the next/prev screen for bigger movements).
	 */
	private void snapToDestination() {
		final int m_screenWidth = getWidth();
		int m_scrollX = getScrollX();
		int m_whichScreen = m_currentScreen;
		int m_deltaX = m_scrollX - (m_screenWidth * m_currentScreen);
		// Check if they want to go to the prev. screen
		if ((m_deltaX < 0)
				&& m_currentScreen != 0
				&& ((m_screenWidth / FRACTION_OF_SCREEN_WIDTH_FOR_SWIPE) < -m_deltaX)) {
			m_whichScreen--;
			// Check if they want to go to the next screen
		} else if ((m_deltaX > 0)
				&& (m_currentScreen + 1 != getChildCount())
				&& ((m_screenWidth / FRACTION_OF_SCREEN_WIDTH_FOR_SWIPE) < m_deltaX)) {
			m_whichScreen++;
		}
		snapToScreen(m_whichScreen);
	}
	/**
	 * Snap to a specific screen, animating automatically for a duration
	 * proportional to the distance left to scroll.
	 * 
	 * @param p_whichScreen
	 *            Screen to snap to
	 */
	private void snapToScreen(final int p_whichScreen) {
		snapToScreen(p_whichScreen, -1);
	}
	/**
	 * Snaps to a specific screen, animating for a specific amount of time to
	 * get there.
	 * 
	 * @param p_whichScreen
	 *            Screen to snap to
	 * @param p_duration
	 *            -1 to automatically time it based on scroll distance; a
	 *            positive number to make the scroll take an exact duration.
	 */
	private void snapToScreen(final int p_whichScreen, final int p_duration) {
		/*
		 * Modified by Yoni Samlan: Allow new snapping even during an ongoing
		 * scroll animation. This is intended to make HorizontalPager work as
		 * expected when used in conjunction with a RadioGroup used as "tabbed"
		 * controls. Also, make the animation take a percentage of our normal
		 * animation time, depending how far they've already scrolled.
		 */
		m_nextScreen = Math
				.max(0, Math.min(p_whichScreen, getChildCount() - 1));
		final int m_newX = m_nextScreen * getWidth();
		final int m_delta = m_newX - getScrollX();
		if (p_duration < 0) {
			// E.g. if they've scrolled 80% of the way, only animation for 20%
			// of the duration
			m_scroller
			.startScroll(
					getScrollX(),
					0,
					m_delta,
					0,
					(int) (Math.abs(m_delta) / (float) getWidth() * ANIMATION_SCREEN_SET_DURATION_MILLIS));
		} else {
			m_scroller.startScroll(getScrollX(), 0, m_delta, 0, p_duration);
		}
		// sets the drawables when the user swipes
		setActiveInactiveDrawables(p_whichScreen);
		// redraw screen
		invalidate();
	}
	/**
	 * Allways called when the user swipes to another view. Gets the current
	 * view an sets the active drawable / shape to the curretn view. (in the
	 * page control)
	 */
	public void setActiveInactiveDrawables(int p_whichScreen) {
		// Getting the Linear Layout where the page control drawables are inside
		LinearLayout m_linLayout = (LinearLayout) ((ViewGroup) this.getParent())
				.getChildAt(((LinearLayout) this.getParent()).getChildCount() - 1);
		// get every imageview and set the one of the current screen to active
		for (int m_i = 0; m_i < this.getChildCount(); m_i++) {
			ImageView m_imgView = (ImageView) m_linLayout.getChildAt(m_i);
			if (m_i == p_whichScreen) {
				m_imgView.setBackgroundDrawable(m_activeDrawable);
			} else {
				m_imgView.setBackgroundDrawable(m_inactiveDrawable);
			}
		}
	}
	/**
	 * Listener for the event that the HorizontalPager switches to a new view.
	 */
	public static interface OnScreenSwitchListener {
		/**
		 * Notifies listeners about the new screen. Runs after the animation
		 * completed.
		 * 
		 * @param screen
		 *            The new screen index.
		 */
		void onScreenSwitched(int screen);
	}
	/**
	 * Builds the active and inactive shapes / drawables for the page control
	 */
	private void makeShapes() {
		m_activeDrawable = new ShapeDrawable();
		m_inactiveDrawable = new ShapeDrawable();
		m_activeDrawable.setBounds(0, 0, (int) m_indicatorSize,
				(int) m_indicatorSize);
		m_inactiveDrawable.setBounds(0, 0, (int) m_indicatorSize,
				(int) m_indicatorSize);
		int m_i[] = new int[2];
		m_i[0] = android.R.attr.textColorSecondary;
		m_i[1] = android.R.attr.textColorSecondaryInverse;
		TypedArray a = getContext().getTheme().obtainStyledAttributes(m_i);
		Shape m_s1 = new OvalShape();
		m_s1.resize(m_indicatorSize, m_indicatorSize);
		Shape m_s2 = new OvalShape();
		m_s2.resize(m_indicatorSize, m_indicatorSize);
		((ShapeDrawable) m_activeDrawable).getPaint().setColor(
				a.getColor(0, Color.DKGRAY));
		((ShapeDrawable) m_inactiveDrawable).getPaint().setColor(
				a.getColor(1, Color.LTGRAY));
		((ShapeDrawable) m_activeDrawable).setShape(m_s1);
		((ShapeDrawable) m_inactiveDrawable).setShape(m_s2);
	}
	/**
	 * Called by the Activity when all Views are added to the horizontal pager
	 * to
	 */
	public void addPagerControl() {
		ViewGroup m_view = (ViewGroup) this.getParent();
		m_view.addView(initPageControl());
	}
	private LinearLayout initPageControl() {
		LinearLayout m_linearLayout = new LinearLayout(getContext());
		LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
				LinearLayout.LayoutParams.FILL_PARENT,
				LinearLayout.LayoutParams.FILL_PARENT, 2f);
		m_linearLayout.setLayoutParams(params);
		m_linearLayout.setOrientation(LinearLayout.HORIZONTAL);
		m_linearLayout.setGravity(Gravity.CENTER_HORIZONTAL
				| Gravity.CENTER_VERTICAL);
		m_linearLayout.setBackgroundColor(Color.BLACK);
		return setPageCount(this.getChildCount(), m_linearLayout);
	}
	/**
	 * Initializes the page control layout at be bottom of the news view. Draws
	 * all page control shapes an set the active shape to the first view
	 * 
	 * @param p_pageCount
	 *            the cout of the pages the user can swipe to
	 * @param p_linearLayout2
	 *            the page control linearlayout
	 * @return the given layout filled with the page control shapes
	 */
	private LinearLayout setPageCount(int p_pageCount,
			LinearLayout p_linearLayout2) {
		for (int m_i = 0; m_i < p_pageCount; m_i++) {
			final ImageView m_imageView = new ImageView(getContext());
			LinearLayout.LayoutParams m_params = new LinearLayout.LayoutParams(
					(int) m_indicatorSize, (int) m_indicatorSize);
			m_params.setMargins((int) m_indicatorSize / 2,
					(int) m_indicatorSize, (int) m_indicatorSize / 2,
					(int) m_indicatorSize);
			m_imageView.setLayoutParams(m_params);
			m_imageView.setBackgroundDrawable(m_inactiveDrawable);
			if (m_i == 0) {
				m_imageView.setBackgroundDrawable(m_activeDrawable);
			}
			p_linearLayout2.addView(m_imageView);
		}
		return p_linearLayout2;
	}
}
